/*
 * @(#)VisibilityFocusTraversalPolicy.java 2/28/2012
 *
 * Copyright 2002 - 2012 JIDE Software Inc. All rights reserved.
 */

package com.element.swing;

import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Set;

/**
 * The focus traversal policy to screen out those components that are not actually painted in the target container.
 *
 * @since 3.3.6
 */
public class VisibilityFocusTraversalPolicy extends FocusTraversalPolicy {
	private FocusTraversalPolicy _defaultPolicy;
	private Set<Container> _containers;

	/**
	 * The constructor.
	 *
	 * @param defaultPolicy the default FocusTraversalPolicy
	 * @since 3.4.0
	 */
	public VisibilityFocusTraversalPolicy(FocusTraversalPolicy defaultPolicy) {
		this(defaultPolicy, null);
	}

	/**
	 * The constructor.
	 *
	 * @param defaultPolicy the default FocusTraversalPolicy
	 * @param container     the container to check the visibility of its child components
	 */
	public VisibilityFocusTraversalPolicy(FocusTraversalPolicy defaultPolicy, Container container) {
		super();
		_defaultPolicy = defaultPolicy;
		if (container != null) {
			_containers = new HashSet<>();
			_containers.add(container);
		}
	}

	/**
	 * Adds the container that needs to check the visibility of its child component.
	 *
	 * @param container the container
	 * @since 3.4.0
	 */
	public void addContainer(Container container) {
		if (_containers == null) {
			_containers = new HashSet<>();
		}
		_containers.add(container);
	}

	/**
	 * Removes the container that needs to check the visibility of its child component.
	 *
	 * @param container the container
	 * @since 3.4.0
	 */
	public void removeContainer(Container container) {
		if (_containers != null) {
			_containers.remove(container);
		}
	}

	/**
	 * Gets all the containers that need to check the visibility of its child component.
	 *
	 * @return the container array.
	 * @since 3.4.0
	 */
	public Container[] getContainers() {
		if (_containers == null) {
			return new Container[0];
		} else {
			return _containers.toArray(new Container[0]);
		}
	}

	/**
	 * Gets the default FocusTraversalPolicy
	 *
	 * @return the default FocusTraversalPolicy.
	 */
	public FocusTraversalPolicy getDefaultPolicy() {
		return _defaultPolicy;
	}

	@Override
	public Component getComponentAfter(Container aContainer, Component aComponent) {
		if (_defaultPolicy == null) {
			return null;
		}
		Component component = _defaultPolicy.getComponentAfter(aContainer, aComponent);
		boolean componentChanged = true;
		while (component != null && aComponent != component && componentChanged) {
			componentChanged = false;
			for (Container container : _containers) {
				if (container.isAncestorOf(component)) {
					Rectangle collapsiblePaneBounds = container.getBounds();
					Container parent = container.getParent();
					Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
					if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
						return component;
					}
					component = _defaultPolicy.getComponentAfter(aContainer, component);
					componentChanged = true;
					break;
				}
			}
		}
		return component;
	}

	@Override
	public Component getComponentBefore(Container aContainer, Component aComponent) {
		if (_defaultPolicy == null) {
			return null;
		}
		Component component = _defaultPolicy.getComponentBefore(aContainer, aComponent);
		boolean componentChanged = true;
		while (component != null && aComponent != component && componentChanged) {
			componentChanged = false;
			for (Container container : _containers) {
				if (container.isAncestorOf(component)) {
					Rectangle collapsiblePaneBounds = container.getBounds();
					Container parent = container.getParent();
					Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
					if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
						return component;
					}
					component = _defaultPolicy.getComponentBefore(aContainer, component);
					componentChanged = true;
					break;
				}
			}
		}
		return component;
	}

	@Override
	public Component getFirstComponent(Container aContainer) {
		if (_defaultPolicy == null) {
			return null;
		}
		Component component = _defaultPolicy.getFirstComponent(aContainer);
		Component aComponent = component;
		boolean componentChanged;
		do {
			componentChanged = false;
			for (Container container : _containers) {
				if (container.isAncestorOf(component)) {
					Rectangle collapsiblePaneBounds = container.getBounds();
					Container parent = container.getParent();
					Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
					if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
						return component;
					}
					component = _defaultPolicy.getComponentAfter(aContainer, component);
					componentChanged = true;
					break;
				}
			}
		}
		while (component != null && aComponent != component && componentChanged);
		return component;
	}

	@Override
	public Component getLastComponent(Container aContainer) {
		if (_defaultPolicy == null) {
			return null;
		}
		Component component = _defaultPolicy.getLastComponent(aContainer);
		Component aComponent = component;
		boolean componentChanged;
		do {
			componentChanged = false;
			for (Container container : _containers) {
				if (container.isAncestorOf(component)) {
					Rectangle collapsiblePaneBounds = container.getBounds();
					Container parent = container.getParent();
					Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
					if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
						return component;
					}
					component = _defaultPolicy.getComponentBefore(aContainer, component);
					componentChanged = true;
					break;
				}
			}
		}
		while (component != null && aComponent != component && componentChanged);
		return component;
	}

	@Override
	public Component getDefaultComponent(Container aContainer) {
		if (_defaultPolicy == null) {
			return null;
		}
		Component component = _defaultPolicy.getDefaultComponent(aContainer);
		Component aComponent = component;
		boolean old = _defaultPolicy instanceof SortingFocusTraversalPolicy && ((SortingFocusTraversalPolicy) _defaultPolicy).getImplicitDownCycleTraversal();
		if (_defaultPolicy instanceof SortingFocusTraversalPolicy) {
			((SortingFocusTraversalPolicy) _defaultPolicy).setImplicitDownCycleTraversal(false);
		}
		try {
			boolean componentChanged;
			do {
				componentChanged = false;
				for (Container container : _containers) {
					if (container.isAncestorOf(component)) {
						Rectangle collapsiblePaneBounds = container.getBounds();
						Container parent = container.getParent();
						Rectangle bounds = SwingUtilities.convertRectangle(component.getParent(), component.getBounds(), parent);
						if (bounds.x < collapsiblePaneBounds.x + collapsiblePaneBounds.width && bounds.x + bounds.width > collapsiblePaneBounds.x && bounds.y < collapsiblePaneBounds.y + collapsiblePaneBounds.height && bounds.y + bounds.height > collapsiblePaneBounds.y) {
							return component;
						}
						component = _defaultPolicy.getComponentAfter(aContainer, component);
						componentChanged = true;
						break;
					}
				}
			}
			while (component != null && aComponent != component && componentChanged);
			return component;
		} finally {
			if (_defaultPolicy instanceof SortingFocusTraversalPolicy) {
				((SortingFocusTraversalPolicy) _defaultPolicy).setImplicitDownCycleTraversal(old);
			}
		}
	}

	@Override
	public Component getInitialComponent(Window window) {
		if (_defaultPolicy == null) {
			return null;
		}
		return _defaultPolicy.getInitialComponent(window);
	}
}
